JavaScript 闭包

JavaScript闭包

概述

一般可理解为函数(函数outerFunc())内的函数(innerFunc()),innerFunc函数可以访问其原型链(包括outerFunc())上的属性和方法。

JavaScript 没有块级作用域,只有函数作用域,所以闭包的使用与函数紧密相关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Counter(start) {
var count = start; // 自由变量
return {
increment: function() { // 使用自由变量的函数,运行时成为闭包
count++;
},
get: function() {
return count;
}
}
}

var foo = Counter(4); // 这里没用new关键字
foo.increment(); // 调用了一次increment方法,该方法搜索到原型链上最近的count值为4,于是count++,执行完后值为5
foo.get(); // 5
foo.increment(); // 再执行一次count++操作
log(foo.get()); // 6

这里 Counter 返回两个闭包,即increment()get(),这两个函数保持对外部函数Counter()的访问。

JS 不存在块级作用域,下例:

1
2
3
4
5
for ( var i = 0; i < 10; i++ ) {
setTimeout(function() {
console.log(i);
}, 1000);
} // 连续打印十遍10

此时先执行10次循环,同时i累积到10,然后约1000毫秒后,连续执行十次console.log(i),自然结果是十个10了。

为了避免上面的错误,可以用函数将变量作用域包围起来,方案一如下:

1
2
3
4
5
6
7
for (var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}

上面立即匿名执行函数将变量i传递到函数内,等待1秒后再将该变量打印出来,这也是在for循环内创建作用域的常见方式。

上面代码换一换,也能完成相同任务:

1
2
3
4
5
6
7
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}

或是这样:

1
2
3
4
5
for(var i = 0; i < 10; i++) {
setTimeout(function(e) {
console.log(e);
}, 1000, i);
}

或是用bind():

1
2
3
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}

总之,当要使用块级作用域时,我最推荐方案一,简单直白。

本文转载自JavaScript花园的闭包章节。

又看了个闭包的博客,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
document.write(funcs[i]() + "<br />");
}

我琢磨了下,改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
// 每个数组项保存了一个立即执行函数,该立即执行函数保存了当前i的值,并返回给数组项
result[i] = (function(){
return i;
})();
}
return result;
}
var funcs = createFunctions();
// 第二个for循环,用于打印出结果
for (var i=0; i < funcs.length; i++){
document.write(funcs[i] + "<br />");
}

结果是一样的,都是使用立即执行函数将每次for循环的结果保存在result数组中,只是原代码中返回的数组,数组项是函数,然后由函数返回数值。我写的更简单一点,数组项直接是数值了。如果第4行的赋值是:

1
2
3
result[i] = function() {
return i;
};

那么数组中保存的就是=后面的匿名函数,当第二个for循环执行时,因为createFunctions()中的i的值是10,所以结果就是十个10了。

另外一个例子:

1
2
3
4
5
6
7
8
9
10
function increment() {
var num = 1;
return function() {
console.log(num++);
};
}
var i = increment(); // 依然没用new关键字
i(); //1
i(); //2
i(); //3

第一次运行i()时,num的值是1,所以打印结果1。第二次运行时,其实只运行了function() {console.log(num++);}这一段代码,而不是整个increment(),所以第二次第三次运行时根本没有var num = 1这个过程。

PS:闭包不能在window的上下文中访问,要通过Function.prototype.bind来实现?